﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Natur.MapManager;

namespace Natur
{
    public enum ItemType { Circuit = 0, Munitions, PowerCore, Remote, ScrapMetal, Wires }
    public enum MoveType { Float = 0, Fly, Run, Slide, Swim, Walk }
    public enum AIMode { Human = 0, Searching }

    public class Item : SelectableStructure
    {
        private string name;
        private ItemType type;

        public Item() : base() { name = null; }

        public Item(string itemName, ItemType newType, Vector3 newPos, Vector3 newForward, Vector3 newUp)
            : base(newPos, newForward, newUp)
        { name = itemName; type = newType; }

        public string Name { get { return name; } }
        public ItemType ItemType { get { return type; } }
    }

    public class Unit : SelectableStructure
    {
        #region STANDARD
        const float THRESH_BOUNCE = .01f;
        const float GRAVITY = 18;

        // Bound constants
        public const int BOUND_AREA = 0;
        public const int BOUND_HEAD = 1;
        public const int BOUND_FEET = 2;
        public const int BOUND_BODY = 3;
        public const int BOUND_COUNT = 4;

        private string name;
        private int unitID;

        public float Height;
        protected BoundingSphere[] aryBounds;

        private Vector3 basePos;
        private Vector3 baseForward;
        private Vector3 baseUp;
        protected Vector3 look;

        private MoveType movementType;
        private MoveType previousMovement;
        public float StepHeight;
        public float StepLength;
        protected float untilStep = 0;
        protected float stepAdjust = 0;

        private float speed = 0;
        private float friction = 0;
        private float glide = 0;

        public float FloatSpeed;
        public float FlySpeed;
        public float RunSpeed;
        public float SwimSpeed;
        public float WalkSpeed;

        public float TurnSpeed;
        public float LookSpeed;

        public bool OnGround = false;
        public Vector3 GroundNormal = Vector3.Zero;

        private float forceGravity = 0;
        private Vector3 movementForce = Vector3.Zero;
        private Vector3 externalForces = Vector3.Zero;
        private Vector3Set velocity = new Vector3Set(Vector3.Zero);

        private Vector3 moveBuffer = Vector3.Zero;
        private float turnBuffer = 0;
        private float lookBuffer = 0;
        private float dstTraveled = 0;

        private bool moveInProgress = false;
        #endregion

        private int[] aryItems = new int[6];
        public AIMode AIMode;

        public Unit(string itemName, int newID, Vector3 newPos, Vector3 newForward, Vector3 newUp, float newHeight, MoveType newMovementType,
            float newWalkSpeed, float newRunSpeed, float newSwimSpeed, float newFloatSpeed, float newFlySpeed, float newTurnSpeed, float newLookSpeed, AIMode newMode)
            : base(newPos, newForward, newUp)
        {
            name = itemName;
            unitID = newID;

            // Default all bounds to area bounds
            aryBounds = new BoundingSphere[BOUND_COUNT];
            for (int i = 0; i < aryBounds.Length; i++)
            { aryBounds[i] = new BoundingSphere(newPos, newHeight / 2); }

            basePos = newPos;
            baseForward = newForward;
            baseUp = newUp;
            look = newForward;

            FloatSpeed = newFloatSpeed;
            FlySpeed = newFlySpeed;
            RunSpeed = newRunSpeed; ;
            SwimSpeed = newSwimSpeed;
            WalkSpeed = newWalkSpeed;

            TurnSpeed = newTurnSpeed;
            LookSpeed = newLookSpeed;

            Height = newHeight;
            StepHeight = Height * .001f;
            StepLength = Height * .05f;
            movementType = newMovementType; // Ensures correct previous type
            MovementType = newMovementType;
            AIMode = newMode;

            // Clear Item amounts
            for (int i = 0; i < aryItems.Length; i++)
            { aryItems[i] = 0; }
        }

        // ***** ACCESSORS *****

        public string Name { get { return name; } }
        public int UnitID { get { return unitID; } }

        public BoundingSphere Bound { get { return aryBounds[BOUND_AREA]; } }
        public BoundingSphere[] Bounds { get { return aryBounds; } }

        public new Vector3 Position { get { return position; } set { position = value; UpdateDimensions(); } }
        public Vector3 Velocity { get { return velocity.CurrentVector; } }
        public Vector3 Direction { get { return Vector3.Normalize(velocity.CurrentVector); } }
        public Vector3 Look { get { return look; } }

        public float BaseSpeed { get { return speed; } }
        public float BaseFriction { get { return friction; } }
        public float DistanceTraveled { get { return dstTraveled; } }
        public MoveType MovementType
        {
            get { return movementType; }
            set
            {
                previousMovement = movementType;
                movementType = value;
                stepAdjust = 0;
                untilStep = 0;
                switch (movementType)
                {
                    case MoveType.Float:
                        speed = FloatSpeed;
                        friction = .5f;
                        glide = .4f;
                        break;
                    case MoveType.Fly:
                        speed = FlySpeed;
                        friction = .5f;
                        glide = .9f;
                        break;
                    case MoveType.Run:
                        speed = RunSpeed;
                        friction = .3f;
                        glide = .9999f;
                        break;
                    case MoveType.Slide:
                        speed = velocity.CurrentVector.Length();
                        friction = .9f;
                        glide = .9999f;
                        break;
                    case MoveType.Walk:
                        speed = WalkSpeed;
                        friction = .3f;
                        glide = .9999f;
                        break;
                }
            }
        }

        public int ItemCount(ItemType searchType) { return aryItems[(int)searchType]; }

        // ***** USER METHODS *****

        public void LookVertical(float ticks) { lookBuffer += ticks * LookSpeed; }
        public void Fly(float ticks) { moveBuffer += ticks * speed * up; moveInProgress = true; }
        public void Jump()
        {
            if (OnGround)
            {
                float cSpeed = velocity.CurrentVector.LengthSquared();
                if (cSpeed > 0)
                { movementForce += 2 * Height * Vector3.Lerp(Vector3.Up, velocity.CurrentVector, .2f); }
                else { movementForce += 2 * Height * Vector3.Up; }
                OnGround = false;
            }
        }
        public void Stride(float ticks)
        {
            if ((movementType != MoveType.Slide) && ((movementType == MoveType.Fly) || (movementType == MoveType.Float) || OnGround))
            { moveBuffer += ticks * speed * forward; moveInProgress = true; }
        }
        public void Strafe(float ticks)
        {
            if ((movementType != MoveType.Slide) && ((movementType == MoveType.Fly) || (movementType == MoveType.Float) || OnGround))
            { moveBuffer += ticks * speed * Left; moveInProgress = true; }
        }
        public void Turn(float ticks) { turnBuffer += ticks * TurnSpeed; }

        public void ApplyForce(Vector3 newForce) { externalForces += newForce; }

        public void ChangeUpdate(CollisionData newData)
        {
            Vector3 currentDirection = Direction;
            float currentSpeed = velocity.CurrentVector.Length();
            bool groundHit = false;

            for (int i = 0; i < newData.WorldSurfaces.Length; i++)
            {
                // Ground bounds have special properties
                if (Vector3.Dot(Vector3.Up, newData.WorldSurfaces[i]) > .5f)
                { groundHit = true; GroundNormal = newData.WorldSurfaces[i]; }

                Vector3 reflection = Vector3.Reflect(currentDirection, newData.WorldSurfaces[i]);
                velocity.CurrentVector = currentSpeed * reflection;

                // Apply collision physics
                Vector3 vX;
                Vector3 vY = Vector3.Dot(velocity.CurrentVector, newData.WorldSurfaces[i]) * newData.WorldSurfaces[i];
                float dForce = vY.Length();

                // Friction
                vX = (velocity.CurrentVector - vY) * friction * (1 - newData.Frictions[i]);
                // Elasticity
                vY *= dForce * newData.Elasticities[i] * Math.Abs(Vector3.Dot(currentDirection, reflection));

                // Update
                velocity.CurrentVector = vX + vY;
                currentSpeed = velocity.CurrentVector.Length();
                currentDirection = Vector3.Normalize(velocity.CurrentVector);

                // Adjust to edge of plane
                Vector3 planeAdjust = velocity.CurrentVector + newData.HitDistances[i].Value * newData.WorldSurfaces[i];
                for (int a = 0; a < aryBounds.Length; a++)
                { aryBounds[a].Center += planeAdjust; }
                position += planeAdjust;
            }
            // Finalize
            OnGround = groundHit;
        }

        public Unit Clone()
        {
            Unit returnUnit = new Unit(name, unitID, position, forward, up, Height, movementType, WalkSpeed, RunSpeed, SwimSpeed,
                FloatSpeed, FlySpeed, TurnSpeed, LookSpeed, AIMode.Searching);
            returnUnit.WalkSpeed = WalkSpeed;
            return returnUnit;
        }

        public virtual void CommitUpdate()
        {
            float dSpeed = velocity.CurrentVector.Length();
            basePos = position;

            // Movement from stepping
            if (moveInProgress && ((MovementType == MoveType.Walk) || (MovementType == MoveType.Run)))
            {
                if (untilStep > StepLength / 2)
                { stepAdjust += dSpeed * StepHeight / StepLength; }
                else if (untilStep > 0)
                { stepAdjust -= dSpeed * StepHeight / StepLength; }
                else
                { untilStep = StepLength; }
                untilStep -= dSpeed;
            }

            // Stabilize velocity
            //if (dSpeed < THRESH_BOUNCE)
            //{ velocity = new Vector3Set(Vector3.Zero); isMoving = false; }

            // Clear movement buffers
            if (OnGround)
            { forceGravity = 0; }
            externalForces = Vector3.Zero;
            moveInProgress = false;
        }

        public void GiveItem(ItemType newType, int newAmount)
        { aryItems[(int)newType] += newAmount; }

        public void RestoreMovementType()
        { MovementType = previousMovement; }

        public void SetBound(int playerBoundIndex, BoundingSphere newBounds)
        { aryBounds[playerBoundIndex] = newBounds; }

        public void Shove(Vector3 newForce)
        {
            position += newForce;
            for (int i = 0; i < aryBounds.Length; i++)
            { aryBounds[i].Center += newForce; }
        }

        public virtual void Update(float mSeconds)
        {
            // Turns
            if (turnBuffer != 0)
            {
                Matrix rotMat = Matrix.CreateFromAxisAngle(up, turnBuffer * mSeconds);
                forward = Vector3.TransformNormal(forward, rotMat);
                look = Vector3.TransformNormal(look, rotMat);
                turnBuffer = 0;
            }

            // Looks
            if (lookBuffer != 0)
            {
                Matrix rotMat = Matrix.CreateFromAxisAngle(Left, -lookBuffer * mSeconds);
                Vector3 newLook = Vector3.TransformNormal(look, rotMat);

                // Restrain look range
                if (Math.Abs(Vector3.Dot(newLook, up)) < .95f)
                { look = newLook; }
                lookBuffer = 0;
            }

            // Update movement
            if (moveInProgress)
            { movementForce += moveBuffer; moveBuffer = Vector3.Zero; }

            // Gravity
            switch (MovementType)
            {
                case MoveType.Run:
                case MoveType.Slide:
                case MoveType.Walk:
                    forceGravity = glide * (forceGravity + GRAVITY * mSeconds);
                    break;
            }

            // Movement friction
            movementForce *= glide;
            if (OnGround)
            { movementForce *= friction; }

            // Physics with glide
            velocity.CurrentVector = (velocity.CurrentVector + movementForce + (forceGravity * Vector3.Down) + externalForces) * mSeconds;

            // Apply
            position += velocity.CurrentVector;
            for (int i = 0; i < aryBounds.Length; i++)
            { aryBounds[i].Center += velocity.CurrentVector; }

            // Require ground check
            OnGround = false;
        }

        public void UpdateDimensions()
        {
            StepHeight = Height * .05f;
            StepLength = Height * .8f;

            aryBounds[BOUND_FEET].Center = position - (((Height / 2) - aryBounds[BOUND_FEET].Radius) * up);
            aryBounds[BOUND_BODY].Center = position;
            aryBounds[BOUND_HEAD].Center = position + (((Height / 2) - aryBounds[BOUND_HEAD].Radius) * up);
        }
    }

    public class Player : Unit
    {
        private float swingSpeed = 6;
        private float windTimeLeft = 1f;
        private float windTimeRight = 1f;
        private float currentWindLeft = 0;
        private float currentWindRight = 0;
        private float currentRollRight = 0;

        private float swingSpeedLeft = 6;
        private float swingSpeedRight = 8;
        private bool swingingLeft = false;
        private bool windingLeft = false;
        private bool swingingRight = false;
        private bool windingRight = false;

        private Vector3 leftPos;
        private Vector3 rightPos;

        public Player(string playerName, int playerIndex, Vector3 newPosition, Vector3 newForward, Vector3 newUp, float newHeight, MoveType newMovementType,
            float newWalkSpeed, float newRunSpeed, float newSwimSpeed, float newFloatSpeed, float newFlySpeed, float newTurnSpeed, float newLookSpeed)
            : base(playerName, playerIndex, newPosition, newForward, newUp, newHeight, newMovementType,
                   newWalkSpeed, newRunSpeed, newSwimSpeed, newFloatSpeed, newFlySpeed, newTurnSpeed, newLookSpeed, AIMode.Human)
        {
            leftPos = new Vector3(-.01f * newHeight, -.0135f * newHeight, -.007f * newHeight);
            rightPos = new Vector3(.01f * newHeight, -.01f * newHeight, -.01f * newHeight);
        }

        // ***** ACCESSORS *****

        public Matrix LookAt { get { return Matrix.CreateLookAt(aryBounds[BOUND_HEAD].Center + (stepAdjust * up), aryBounds[BOUND_HEAD].Center + (stepAdjust * up) + look, up); } }

        public Matrix LeftWorld { get { return Matrix.CreateFromAxisAngle(Vector3.Left, -currentWindLeft) * Matrix.CreateTranslation(leftPos) * Matrix.CreateWorld(position, look, up); } }

        public Matrix RightWorld
        { get { return Matrix.CreateFromAxisAngle(Vector3.Forward, currentRollRight) * Matrix.CreateFromAxisAngle(Vector3.Left, -currentWindRight) * Matrix.CreateTranslation(rightPos) * Matrix.CreateWorld(position, look, up); } }

        // ***** USER METHODS *****

        public override void CommitUpdate()
        {
            base.CommitUpdate();
        }

        public void Draw()
        {

        }

        public override void Update(float tSeconds)
        {
            // Left hand object
            if (swingingLeft)
            {
                if (windingLeft)
                {
                    if (currentWindLeft < windTimeLeft) { currentWindLeft += swingSpeed * tSeconds; swingSpeedLeft *= .75f; }
                    else { windingLeft = false; }
                }
                else
                {
                    if (currentWindLeft > 0) { currentWindLeft -= swingSpeed * tSeconds; swingSpeedLeft *= 1.25f; }
                    else { currentWindLeft = 0; swingingLeft = false; swingSpeedLeft = swingSpeed; SwingRight(); }
                }
            }

            // Right hand object
            if (swingingRight)
            {
                if (windingRight)
                {
                    if (currentWindRight < windTimeRight)
                    {
                        currentWindRight += swingSpeed * tSeconds;
                        currentRollRight += .5f * swingSpeed * tSeconds;
                        swingSpeedRight *= .75f;
                    }
                    else { windingRight = false; }
                }
                else
                {
                    if (currentWindRight > 0)
                    {
                        currentWindRight -= swingSpeed * tSeconds;
                        swingSpeedRight *= 1.25f;
                    }
                    else { currentWindRight = 0; currentRollRight = 0; swingingRight = false; swingSpeedRight = swingSpeed; }
                }
            }

            base.Update(tSeconds);
        }

        public void SwingLeft()
        {
            if (!swingingLeft)
            {
                currentWindLeft = 0;
                windingLeft = true;
                swingingLeft = true;
            }
        }

        public void SwingRight()
        {
            if (!swingingRight)
            {
                currentWindRight = 0;
                windingRight = true;
                swingingRight = true;
            }
        }
    }

    public struct Vector3Set
    {
        public Vector3 BaseVector;
        public Vector3 CurrentVector;

        public Vector3Set(Vector3 newBase)
        { BaseVector = newBase; CurrentVector = newBase; }

        public void Add(Vector3 newVector)
        { BaseVector += newVector; CurrentVector += newVector; }

        public void Set(Vector3 newVector)
        { BaseVector = newVector; CurrentVector = newVector; }
    }
}
